package com.nebula.core.dsl.operation;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.nebula.core.client.NebulaSession;
import com.nebula.core.dsl.WhereStatement;
import com.nebula.core.dsl.builder.FindPathStatementBuilder;
import com.nebula.core.util.StrFormatter;
import com.vesoft.nebula.client.graph.data.ResultSet;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.nebula.core.constant.Constant.QUOTED_LITERAL_FORMAT;


/**
 * find path 路径查询操作类
 * <p>
 * FIND { SHORTEST | ALL | NOLOOP } PATH
 * FROM <vertex_id_list> TO <vertex_id_list>
 * OVER <edge_type_list> [REVERSELY | BIDIRECT]
 * [UPTO <N> STEPS]
 * [| ORDER BY $-.path]
 * [| LIMIT <M>];
 * <p>
 *
 * @author zhangtao
 */
public class FindPathOperation implements FindPathStatementBuilder.FindPathStatement {
    private NebulaSession nebulaSession;
    private String pathType;
    private String direction = "";
    private boolean withProp = true;
    private WhereStatement where;
    private String pathAlias = "p";
    private List<Object> fromVidList = new ArrayList<>();
    private List<Object> toVidList = new ArrayList<>();
    private List<String> edgeTypeList = new ArrayList<>();
    private Integer steps;
    private SQLLimit limit;

    private FindPathOperation() {
    }

    private FindPathOperation(NebulaSession nebulaSession) {
        this.nebulaSession = nebulaSession;
    }

    protected static FindPathStatementBuilder.FindPathStart create() {
        return new FindPathOperation();
    }

    protected static FindPathStatementBuilder.FindPathStart create(NebulaSession nebulaSession) {
        return new FindPathOperation(nebulaSession);
    }

    @Override
    public FindPathStatementBuilder.FindPathTypeOut all() {
        pathType = "ALL";
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathTypeOut shortest() {
        pathType = "SHORTEST";
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathTypeOut noloop() {
        pathType = "NOLOOP";
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathWithPropOut withProp(boolean withProp) {
        this.withProp = withProp;
        return this;
    }


    @Override
    public FindPathStatementBuilder.FindPathOverOut over(boolean in, boolean out, String... edgeType) {
        if (in && out) {
            this.direction = "BIDIRECT";
        } else if (in) {
            this.direction = "REVERSELY";
        }
        edgeTypeList.addAll(Arrays.stream(edgeType).collect(Collectors.toList()));
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathStepOut steps(int steps) {
        this.steps = steps;
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathLimitOut limit(int pageNo, int pageSize) {
        this.limit = new SQLLimit();
        limit.setOffset(pageSize * (pageNo - 1));
        limit.setRowCount(pageSize);
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathWhereOut and(String key, String operator, Object value) {
        if (this.where == null) {
            this.where = WhereStatement.create();
        }
        this.where.and(key, operator, value);
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathWhereOut or(String key, String operator, Object value) {
        if (this.where == null) {
            this.where = WhereStatement.create();
        }
        this.where.or(key, operator, value);
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathYieldOut yield(String pathAlias) {
        this.pathAlias = pathAlias;
        return this;
    }

    @Override
    public FindPathStatementBuilder.FindPathFromToOut fromTo(List<Object> fromVids, List<Object> toVids) {
        this.fromVidList = fromVids;
        this.toVidList = toVids;
        return this;
    }

    @Override
    public String build() {
        StringBuilder builder = new StringBuilder("FIND ");
        //拼接path类型
        builder.append(pathType);
        builder.append(" PATH ");

        if (withProp) {
            builder.append(" WITH PROP ");
        }

        //拼接起始点vid
        builder.append(fromVidList.stream().map(vid -> {
            if (vid instanceof Integer) {
                return String.valueOf(vid);
            } else {
                return String.format(QUOTED_LITERAL_FORMAT, vid);
            }
        }).collect(Collectors.joining(",", "FROM ", " ")));

        //拼接终止点vid
        builder.append(toVidList.stream().map(vid -> {
            if (vid instanceof Integer) {
                return String.valueOf(vid);
            } else {
                return String.format(QUOTED_LITERAL_FORMAT, vid);
            }
        }).collect(Collectors.joining(",", "TO ", " ")));

        //拼接边类型
        if (CollectionUtils.isEmpty(edgeTypeList)) {
            builder.append(" OVER * ");
        } else {
            builder.append(edgeTypeList.stream().collect(Collectors.joining(",", "OVER ", " ")));
            //拼接边方向
            builder.append(direction);
        }

        //拼接where条件
        if (where != null) {
            builder.append(" WHERE ");
            builder.append(where);
        }

        //拼接步数
        if (steps != null) {
            builder.append(" UPTO ");
            builder.append(steps);
            builder.append(" STEPS ");
        }

        //拼接返回字段
        builder.append(StrFormatter.format(" YIELD PATH AS {}", pathAlias));

        //拼接分页
        if (limit != null) {
            builder.append(" | ");
            builder.append(SQLUtils.toMySqlString(limit));
        }

        return builder.toString();
    }

    @Override
    public ResultSet excute() {
        return nebulaSession.query(this.build());
    }

}
